/*
	Copyright (c) 2008, Grant Jones <grant@podtronics.com>.
	All rights reserved.

	Redistribution and use in source and binary forms, with or
	without modification, are permitted provided that the following
	conditions are met:

		Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
		
		Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in
		the documentation and/or other materials provided with the
		distribution.
		
		Neither the name of the gxjones.com nor the names of its
		contributors may be used to endorse or promote products derived
		from this software without specific prior written permission.
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
	FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
	OF THE POSSIBILITY OF SUCH DAMAGE.
	
	http://www.opensource.org/licenses/bsd-license.php
*/

/*
 *  edb_net_rest.c
 *  edb
 *
 *  Created by Grant Jones on 3/20/08.
 *
 */
#include "edb_main.h"
#include "edb_net_main.h"
#include "edb_net_rest.h"
#include "edb_net_utils.h"

#include "edb_msg_parser.h"
#include "edb_msg_utils.h"

/* this module doesn't do anything if NO_REST_HTTP_SERVER is defined: */
#ifndef NO_REST_HTTP_SERVER

/* the following are functions available to lua for gathering more 
** info from the request info, this are almost all just wrappers
** to functions */

static int edb_lfunc_findHeader( lua_State *luaVM )
{
	edb_rest_req_t *rest_req_ptr = lua_touserdata(luaVM, -2);
	const char *header_key = luaL_checkstring(luaVM, -1);
	const char *header_value = NULL;
	if(!rest_req_ptr)
		return 0;
	header_value = evhttp_find_header(rest_req_ptr->req->input_headers, header_key);
	
	if(!header_value)
		lua_pushnil(luaVM);
	else
		lua_pushstring(luaVM, header_value);
	
	return 1;
}

static int edb_lfunc_addHeader( lua_State *luaVM )
{
	edb_rest_req_t *rest_req_ptr = lua_touserdata(luaVM, -3);
	const char *header_key = luaL_checkstring(luaVM, -2);
	const char *header_value = luaL_checkstring(luaVM, -1);

	if(!rest_req_ptr)
		return 0;
	
	evhttp_add_header(rest_req_ptr->req->output_headers, header_key, header_value);
	return 0;
}
static int edb_lfunc_decodeURI( lua_State *luaVM )
{
	const char *uri = luaL_checkstring(luaVM, -1 );
	mempool_t tmp_pool;
	mempool_init( &tmp_pool, 1024 );
	char *decoded_uri = edb_net_URIDecode(&tmp_pool, (const unsigned char *)uri, strlen(uri) );
	lua_pushstring(luaVM,decoded_uri);
	mempool_destroy(&tmp_pool);
	
	return 1;
}
static int edb_lfunc_decodeURIQuery( lua_State *luaVM )
{
	const char *uri = luaL_checkstring(luaVM, -1);
	if(!uri) return 0;
	mempool_t tmp_pool;
	mempool_init( &tmp_pool, 2048 );
	char *line = edb_strdup(&tmp_pool, uri, strlen(uri), "edb_lfunc_decodeURIQuery");
	char *p = line;
	char *argument;
	
	lua_newtable(luaVM);
	while (p != NULL && *p != '\0')
	{
		char *key, *value;
		argument = strsep(&p, "&");

		value = argument;
		key = strsep(&value, "=");
		if (value == NULL)
			goto error;
		
		
		value = edb_net_URIDecode(&tmp_pool, (const unsigned char *)value, strlen(value));
		key = edb_net_URIDecode(&tmp_pool, (const unsigned char *)key, strlen(key));
		
		lua_pushstring(luaVM, key);
		lua_pushstring(luaVM, value);
		lua_settable(luaVM, -3);
	}
error:
	mempool_destroy(&tmp_pool);
	
	return 1;
}

static int edb_lfunc_getBody( lua_State *luaVM )
{
	edb_rest_req_t *rest_req_ptr = lua_touserdata(luaVM, -1);
	if(!rest_req_ptr)
	{
		lua_pushnil(luaVM);
		return 1;
	}
	
	lua_pushlstring(luaVM, (const char *)EVBUFFER_DATA(rest_req_ptr->req->input_buffer),  EVBUFFER_LENGTH(rest_req_ptr->req->input_buffer));
	return 1;
}

static int edb_lfunc_addBody( lua_State *luaVM )
{
	edb_rest_req_t *rest_req_ptr = lua_touserdata(luaVM, -2);
	size_t body_data_sz;
	const char *body_data = lua_tolstring(luaVM, -1, &body_data_sz);

	if(!rest_req_ptr)
		return 0;
		
	evbuffer_add( rest_req_ptr->evb, body_data, body_data_sz );
	return 0;
}

static int edb_lfunc_query( lua_State *luaVM )
{
	/* query provides a similar interface as those provided by
	** the python and ruby modules:
	**
	** query( query_str, input_items_table )
	** returns: a list of items
	*/
	ProgramContextStruct *pcs = lua_touserdata(luaVM, lua_upvalueindex(1) );	
	const char *query_str = luaL_checkstring( luaVM, -2 );
	size_t in_item_count = 0;
	size_t cur_item = 0;
	size_t cur_attr = 0;
	cmapStruct *result_items = NULL;
	edb_net_item_t *ni;
	cmapEnumStruct e;
	CMAP_KEY_TYPE item_cnt;
		
	mempool_t tmp_pool;
	
	luaL_checktype( luaVM, -1, LUA_TTABLE );
	
	mempool_init( &tmp_pool, 2048 );
	
	in_item_count = lua_objlen(luaVM, -1);
	
/*	printf("%d in_item_count\n", (int)in_item_count );*/
	
	edb_net_item_t *items = mempool_malloc_named(&tmp_pool, sizeof(edb_net_item_t)*in_item_count, "edb_lfunc_query:items" );
	
	/* table is in the stack at index -2 */
	lua_pushnil(luaVM);  /* first key */
	while (lua_next(luaVM, -2) != 0)
	{
		/* uses 'key' (at index -2) and 'value' (at index -1) */
/*		printf("%s - %s\n", lua_typename(luaVM, lua_type(luaVM, -2)), lua_typename(luaVM, lua_type(luaVM, -1)));*/
		
		/* lua_objlen operator would return 0 for dictionary style tables, so we 
		** do it the hard way: */
		items[cur_item].attr_count = 0;
		for( lua_pushnil(luaVM); lua_next(luaVM, -2) != 0; lua_pop(luaVM, 1), items[cur_item].attr_count++);
		
		items[cur_item].attrs = mempool_malloc_named(&tmp_pool, sizeof(edb_net_item_attr_t)*items[cur_item].attr_count, "edb_lfunc_query:attributes" );
		if(!items[cur_item].attrs)
		{
			edb_log_printf(0,"allocation failure");		
			goto cleanup;
		}
		
		cur_attr = 0;
		lua_pushnil(luaVM);
		while (lua_next(luaVM, -2) != 0)
		{
/*			printf("\t%s - %s\n", lua_typename(luaVM, lua_type(luaVM, -2)), lua_typename(luaVM, lua_type(luaVM, -1)));	*/
			const char *key = luaL_checkstring( luaVM, -2 );
			size_t value_sz = 0;
			const char *value = lua_tolstring(luaVM, -1, &value_sz);
			items[cur_item].attrs[cur_attr].key = edb_strdup(&tmp_pool, key, strlen(key), "edb_lfunc_query:attribute key" );
			items[cur_item].attrs[cur_attr].value = mempool_malloc_named(&tmp_pool, value_sz, "edb_lfunc_query:attribute value" );
			if(!items[cur_item].attrs[cur_attr].value || !items[cur_item].attrs[cur_attr].key)
			{
				edb_log_printf(0,"allocation failure");
				goto cleanup;
			}
			memcpy( items[cur_item].attrs[cur_attr].value, value, value_sz );
			items[cur_item].attrs[cur_attr].value_sz = value_sz;
			
			
			lua_pop(luaVM, 1);
			cur_attr++;
		}
		
		
		/* removes 'value'; keeps 'key' for next iteration */
		lua_pop(luaVM, 1);
		cur_item ++;
	}	
	assert(cur_item == in_item_count);
	
#ifdef EDB_VERBOSE_QUERY	
	int x, i;
	printf("%d item%s:\n", (int)in_item_count, (in_item_count==1?"":"s") );
	for( i = 0; i < in_item_count; i ++ )
	{
		printf("\t\t[");
		for( x = 0; x < items[i].attr_count; x ++ )
		{
			printf("%s:", items[i].attrs[x].key );
			print_len( items[i].attrs[x].value, items[i].attrs[x].value_sz );
			if( x != (items[i].attr_count-1) )
				printf(",");
		}
		printf("]\n");
	}			
#endif	
	/* do the actual query */
	result_items = edb_msg_parser(pcs, &tmp_pool, query_str, in_item_count, items);

	/* convert result into lua tables */
	lua_newtable(luaVM);
	
	for( cmap_enum(result_items, &e), cur_item = 0; (ni = cmap_enum_next(&e, &item_cnt)); cur_item ++)
	{
		
		lua_pushnumber(luaVM, cur_item+1);
		lua_newtable(luaVM);
		
		for( cur_attr = 0; cur_attr < ni->attr_count; cur_attr ++ )
		{
			lua_pushstring( luaVM, ni->attrs[cur_attr].key );
			lua_pushlstring(luaVM, ni->attrs[cur_attr].value, ni->attrs[cur_attr].value_sz );
			
			lua_settable(luaVM, -3);
		}
		lua_settable(luaVM, -3);
	}	

cleanup:
	if( !result_items )
	{
		/* an error occured, clear lua's stack and return nil */
		lua_pop(luaVM, lua_gettop(luaVM) );
		lua_pushnil(luaVM);
	}
	mempool_destroy(&tmp_pool);	
	return 1;
}

void edb_net_rest_init( ProgramContextStruct *pcs )
{
	if(pcs->luaVM)
		lua_close( pcs->luaVM );
		
	/* allocate LUA state, this luaVM is only used by the REST API module */
	pcs->luaVM = lua_open();
	if(!pcs->luaVM)
	{
		printf("failed to open lua\n");
		exit(1);
	}

	luaL_openlibs(pcs->luaVM);	

	/* query function needs the pcs for doing a query */
	lua_pushlightuserdata(pcs->luaVM, pcs);
	lua_pushcclosure(pcs->luaVM, edb_lfunc_query, 1);
	lua_setglobal(pcs->luaVM,"query");

	lua_register(pcs->luaVM,"decodeURIQuery", edb_lfunc_decodeURIQuery);
	lua_register(pcs->luaVM,"decodeURI", edb_lfunc_decodeURI);
	
	lua_register(pcs->luaVM,"findHeader", edb_lfunc_findHeader);
	lua_register(pcs->luaVM,"addHeader", edb_lfunc_addHeader);	

	lua_register(pcs->luaVM,"getBody", edb_lfunc_getBody);	/* get request body */
	lua_register(pcs->luaVM,"addBody", edb_lfunc_addBody);	/* add response body */
	
//	lua_register(pcs->luaVM,"findHeader", lfunc_srv_mob_getTargetLocation);

	if( !pcs->net_http_file )
		return;
		
	if( luaL_loadfile(pcs->luaVM, pcs->net_http_file ) != 0 )
	{
		printf("init: %s\n", lua_tostring( pcs->luaVM, -1 ) );
		lua_pop(pcs->luaVM,1);
		return;
	}
	
	if(lua_pcall(pcs->luaVM, 0, LUA_MULTRET, 0))
	{
		printf("init: %s\n", lua_tostring(pcs->luaVM,-1) );
		lua_pop(pcs->luaVM,1);
		return;		
	}	
}



void edb_net_rest_get_req(struct evhttp_request *req, void *ctx)
{
	ProgramContextStruct *pcs = ctx;
	edb_rest_req_t rest_req;

	rest_req.evb = evbuffer_new();
	rest_req.req = req;
	rest_req.pcs = pcs;
	
	lua_getfield( pcs->luaVM, LUA_GLOBALSINDEX, "http_get" );
	/* arg 1: req handle passed to the various helper functions */
	/* arg 2: uri requested */
	lua_pushlightuserdata(pcs->luaVM, &rest_req);
	lua_pushlstring(pcs->luaVM, req->uri, strlen(req->uri) );
	
	if( lua_pcall( pcs->luaVM, 2, 0, 0 ) )
	{
		size_t err_str_sz;
		const char *err_str = lua_tolstring(pcs->luaVM, -1, &err_str_sz);
		evbuffer_add( rest_req.evb, err_str, err_str_sz );
		lua_pop(pcs->luaVM,1);
		
		evhttp_send_reply(req, 500, "Internal Server Error", rest_req.evb );
		goto cleanup;
	}	
	
	evhttp_send_reply(req, HTTP_OK, "OK", rest_req.evb);
	
cleanup:
	evbuffer_free(rest_req.evb);	
}

void edb_net_rest_post_req(struct evhttp_request *req, void *ctx)
{
	ProgramContextStruct *pcs = ctx;
	edb_rest_req_t rest_req;
	
	rest_req.evb = evbuffer_new();
	rest_req.req = req;
	rest_req.pcs = pcs;
	
	lua_getfield( pcs->luaVM, LUA_GLOBALSINDEX, "http_post" );
	/* arg 1: req handle passed to the various helper functions */
	/* arg 2: uri requested */
	lua_pushlightuserdata(pcs->luaVM, &rest_req);
	lua_pushlstring(pcs->luaVM, req->uri, strlen(req->uri) );
	
	if( lua_pcall( pcs->luaVM, 2, 0, 0 ) )
	{
		size_t err_str_sz;
		const char *err_str = lua_tolstring(pcs->luaVM, -1, &err_str_sz);
		evbuffer_add( rest_req.evb, err_str, err_str_sz );
		lua_pop(pcs->luaVM,1);
		
		evhttp_send_reply(req, 500, "Internal Server Error", rest_req.evb );
		goto cleanup;
	}	
	
	evhttp_send_reply(req, HTTP_OK, "OK", rest_req.evb);
	
cleanup:
	evbuffer_free(rest_req.evb);	
}

void edb_net_rest_req(struct evhttp_request *req, void *ctx)
{

#ifdef DUMP_HEADER
struct evkeyval *header;
	edb_log_printf(0,"<pre>");
	for( header = req->input_headers; header; header = header->next.tqe_next )
	{
		edb_log_printf(0, "header : %s = %s\n", header->key, header->value );
	}

	
	
	edb_log_printf(0,"uri: %s\n", req->uri );
	edb_log_printf(0,"input_buffer len: %ld\n", EVBUFFER_LENGTH(req->input_buffer) );
	edb_log_printf(0,"</pre>\n");
#endif

#define EDB_RELOAD_LUA
#ifdef EDB_RELOAD_LUA
	/* forces reinterpreting the lua script, also throws away
	** the old lua vm */
	edb_net_rest_init( ctx );
#endif
	switch( req->type )
	{
	case EVHTTP_REQ_GET:
		edb_net_rest_get_req( req, ctx );
		return;		
	case EVHTTP_REQ_POST:
		edb_net_rest_post_req( req, ctx );
		return;
	default:
		evhttp_send_error( req, 501, "Not Implemented" );
		return;
	}
}




#endif /* #ifndef NO_REST_HTTP_SERVER */




